package org.multi.core;

import java.util.function.Consumer;
import org.multi.core.function.BiCharConsumer;
import org.multi.core.function.CharConsumer;
import org.multi.core.function.CharPredicate;
import org.multi.core.state.BoolState;
import org.multi.core.state.LongState;

public interface CharMulti {

    static void stop() {
        StopException.stop();
    }

    static CharMulti create(Consumer<CharConsumer> consumer) {
        return consumer::accept;
    }

    static CharMulti range(char start, char end) {
        if (start > end) {
            throw new IllegalArgumentException("start must be less than end");
        }
        return consumer -> {
            for (char item = start; item < end; item++) {
                consumer.accept(item);
            }
        };
    }

    static CharMulti rangeClosed(char start, char end) {
        if (start > end) {
            throw new IllegalArgumentException("start must be less than end");
        }
        return consumer -> {
            for (char item = start; item <= end; item++) {
                consumer.accept(item);
            }
        };
    }

    static CharMulti of(Iterable<Character> items) {
        return consumer -> {
            for (Character item : items) {
                consumer.accept(item);
            }
        };
    }

    static CharMulti of(char... items) {
        return consumer -> {
            for (char item : items) {
                consumer.accept(item);
            }
        };
    }

    static CharMulti of(String items) {
        return consumer -> {
            for (char item : items.toCharArray()) {
                consumer.accept(item);
            }
        };
    }

    static CharMulti of(char t) {
        return consumer -> consumer.accept(t);
    }

    void consume(CharConsumer consumer);

    default void consume(CharConsumer onItem, Consumer<Throwable> onFailure) {
        try {
            consume(onItem);
        } catch (Throwable error) {
            onFailure.accept(error);
        }
    }

    default void consumeOn(CharConsumer consumer) {
        try {
            consume(consumer);
        } catch (StopException ignored) {
        }
    }

    default CharMulti filter(CharPredicate test) {
        return consumer -> consume(item -> {
            if (test.test(item)) {
                consumer.accept(item);
            }
        });
    }

    default CharMulti mapMulti(BiCharConsumer<CharConsumer> function) {
        return consumer -> consume(item -> function.accept(item, consumer));
    }

    default CharMulti take(long count) {
        return consumer -> {
            LongState state = new LongState(count);
            consumeOn(item -> {
                consumer.accept(item);
                if (--state.value == 0) {
                    stop();
                }
            });
        };
    }

    default CharMulti takeWhile(CharPredicate test) {
        return consumer -> {
            consumeOn(item -> {
                if (test.test(item)) {
                    consumer.accept(item);
                } else {
                    stop();
                }
            });
        };
    }

    default CharMulti drop(long count) {
        return consumer -> {
            LongState state = new LongState(count);
            consume(item -> {
                if (state.value > 0) {
                    state.value--;
                } else {
                    consumer.accept(item);
                }
            });
        };
    }

    default CharMulti dropWhile(CharPredicate test) {
        return consumer -> {
            boolean[] state = {true};
            consume(item -> {
                if (state[0] && test.test(item)) {
                    return;
                }
                state[0] = false;
                consumer.accept(item);
            });
        };
    }

    default boolean isEmpty() {
        boolean[] state = {true};
        consumeOn(item -> {
            state[0] = false;
            stop();
        });
        return state[0];
    }

    default void forEach(CharConsumer consumer) {
        consume(consumer);
    }

    default boolean anyMatch(CharPredicate test) {
        BoolState state = new BoolState(false);
        consumeOn(item -> {
            if (test.test(item)) {
                state.value = true;
                stop();
            }
        });
        return state.value;
    }

    default boolean allMatch(CharPredicate test) {
        return !anyMatch(test.negate());
    }

    default boolean noneMatch(CharPredicate test) {
        return !allMatch(test);
    }

    default void println() {
        consume(System.out::println);
    }
}